#pragma once

enum eMnemonicInstruction
{
	AAA, // Ascii Adjust for Addition
	AAD, // Ascii Adjust for Division
	AAM, // Ascii Adjust for Multiplication
	AAS, // Ascii Adjust for Subtraction
	ADC, // Add With Carry
	ADD, // Arithmetic Addition
	AND, // Logical And
	ARPL, // Adjusted Requested Privilege Level of Selector
	BOUND, // Array Index Bound Check
	BSF, // Bit Scan Forward
	BSR, // Bit Scan Reverse
	BSWAP, // Byte Swap
	BT, // Bit Test
	BTC, // Bit Test with Compliment
	BTR, // Bit Test with Reset
	BTS, // Bit Test and Set
	CALL, // Procedure Call
	CBW, // Convert Byte to Word
	CDQ, // Convert Double to Quad
	CLC, // Clear Carry
	CLD, // Clear Direction Flag
	CLI, // Clear Interrupt Flag
	CLTS, // Clear Task Switched Flag
	CMC, // Complement Carry Flag
	CMP, // Compare
	CMPS, // Compare String
	CMPXCHG, // Compare and Exchange
	CWD, // Convert Word to Doubleword
	CWDE, // Convert Word to Extended Doubleword
	DAA, // Decimal Adjust for Addition
	DAS, // Decimal Adjust for Subtraction
	DEC, // Decrement
	DIV, // Divide
	ENTER, // Make Stack Frame
	ESC, // Escape
	FLOATING_POINT_INSTRUCTION,
	HLT, // Halt CPU
	IDIV, // Signed Integer Division
	IMUL, // Signed Multiply
	//IN, // Input Byte or Word From Port
	INC, // Increment
	INS, // Input String from Port
	mINT, // Interrupt
	INT1,
	INT3,
	INTO, // Interrupt on Overflow
	INVD, // Invalidate Cache
	INVLPG, // Invalidate Translation Look-Aside Buffer Entry
	IRET, // Interrupt Return
	JA, // Jump Above / Jump Not Below or Equal
	JAE, // Jump Above or Equal / Jump on Not Below
	JB, // Jump Below / Jump Not Above or Equal
	JBE, // Jump Below or Equal / Jump Not Above
	JC, // Jump on Carry
	JCXZ, // Jump if Register (E)CX is Zero
	JE, // Jump Equal / Jump Zero
	JG, // Jump Greater / Jump Not Less or Equal
	JGE, // Jump Greater or Equal / Jump Not Less
	JL, // Jump Less / Jump Not Greater or Equal
	JLE, // Jump Less or Equal / Jump Not Greater
	JMP, // Unconditional Jump
	JNC, // Jump Not Carry
	JNE, // Jump Not Equal / Jump Not Zero
	JNO, // Jump Not Overflow
	JNS, // Jump Not Signed
	JNP, // Jump Not Parity / Jump Parity Odd
	JO, // Jump on Overflow
	JP, // Jump on Parity / Jump on Parity Even
	JS, // Jump Signed
	LAHF, // Load Register AH From Flags
	LAR, // Load Access Rights
	LDS, // Load Pointer Using DS
	LEA, // Load Effective Address
	LEAVE, // Restore Stack for Procedure Exit
	LES, // Load Pointer Using ES
	LFS, // Load Pointer Using FS
	LGDT, // Load Global Descriptor Table
	LIDT, // Load Interrupt Descriptor Table
	LGS, // Load Pointer Using GS
	LLDT, // Load Local Descriptor Table
	LMSW, // Load Machine Status Word
	LOCK, // Lock Bus
	LODS, // Load String
	LOOP, // Decrement CX and Loop if CX Not Zero
	LOOPE, // Loop While Equal / Loop While Zero
	LOOPNZ, // Loop While Not Zero / Loop While Not Equal
	LSL, // Load Segment Limit
	LSS, // Load Pointer Using SS
	LTR, // Load Task Register
	MOV, // Move Byte or Word
	MOVS, // Move String
	MOVSX, // Move with Sign Extend
	MOVZX, // Move with Zero Extend
	MUL, // Unsigned Multiply
	NEG, // Two's Complement Negation
	NOP, // No Operation
	NOT, // One's Compliment Negation
	OR, // Inclusive Logical OR
	//OUT, // Output Data to Port
	OUTS, // Output String to Port
	POP, // Pop Word off Stack
	POPA,
	POPAD, // Pop All Registers onto Stack
	POPF,
	POPFD, // Pop Flags off Stack
	PUSH, // Push Word onto Stack
	PUSHA, // Push All Registers onto Stack
	PUSHF, // Push Flags onto Stack
	RCL, // Rotate Through Carry Left
	RCR, // Rotate Through Carry Right
	REP, // Repeat String Operation
	REPE, // Repeat Equal / Repeat Zero
	REPNE, // Repeat Not Equal / Repeat Not Zero
	RET, // Return From Procedure
	ROL, // Rotate Left
	ROR, // Rotate Right
	SAHF, // Store AH Register into FLAGS
	SAL, // Shift Arithmetic Left / Shift Logical Left
	SALC,
	SAR, // Shift Arithmetic Right
	SBB, // Subtract with Borrow
	SCAS, // Scan String
	SETAE, // Set if Above or Equal / Set if Not Below
	SETB, // Set if Below / Set if Not Above or Equal
	SETBE, // Set if Below or Equal / Set if Not Above
	SETE, // Set if Equal / Set if Zero
	SETNE, // Set if Not Equal / Set if Not Zero
	SETL, // Set if Less / Set if Not Greater or Equal
	SETGE, // Set if Greater or Equal / Set if Not Less
	SETLE, // Set if Less or Equal / Set if Not greater or Equal
	SETG, // Set if Greater / Set if Not Less or Equal
	SETS, // Set if Signed
	SETNS, // Set if Not Signed
	SETC, // Set if Carry
	SETNC, // Set if Not Carry
	SETO, // Set if Overflow
	SETNO, // Set if Not Overflow
	SETP, // Set if Parity / Set if Parity Even
	SETNP, // Set if No Parity / Set if Parity Odd
	SGDT, // Store Global Descriptor Table
	SIDT, // Store Interrupt Descriptor Table
	SHR, // Shift Logical Right
	SHLD, // Double Precision Shift
	SLDT, // Store Local Descriptor Table
	SMSW, // Store Machine Status Word
	STC, // Set Carry
	STD, // Set Direction Flag
	STI, // Set Interrupt Flag
	STOS, // Store String
	STR, // Store Task Register
	SUB, // Subtract
	TEST, // Test For Bit Pattern
	VERR, // Verify Read
	VERW, // Verify Write
	WAIT, // Event Wait
	WBINVD, // Write-Back and Invalidate Cache
	XCHG, // Exchange
	XLAT, // Translate
	XOR, // Exclusive OR
	NUM_MNEMONICS,
	BAD_INSTRUCTION = -1
};

enum eOpcode
{
	OPCODE_ADD_EB_GB,
	OPCODE_ADD_EV_GV,
	OPCODE_ADD_GB_EB,
	OPCODE_ADD_GV_EV,
	OPCODE_ADD_AL_IB,
	OPCODE_ADD_AX_IV,
	OPCODE_PUSH_ES,
	OPCODE_POP_ES,
	OPCODE_PUSH_CS = 0xE,

	OPCODE_DAA = 0x27,
	OPCODE_DAS = 0x2F,
	OPCODE_AAA = 0x37,
	OPCODE_AAS = 0x3F,
	OPCODE_INC_AX = 0x40,
	OPCODE_INC_CX,
	OPCODE_INC_BX,
	OPCODE_INC_DX,
	OPCODE_INC_SP,
	OPCODE_INC_BP,
	OPCODE_INC_SI,
	OPCODE_INC_DI,
	OPCODE_DEC_AX,
	OPCODE_DEC_CX,
	OPCODE_DEC_BX,
	OPCODE_DEC_DX,
	OPCODE_DEC_SP,
	OPCODE_DEC_BP,
	OPCODE_DEC_SI,
	OPCODE_DEC_DI,
	OPCODE_PUSH,
	OPCODE_POP = 0x58,
	OPCODE_POP_CX,
	OPCODE_POP_DX,
	OPCODE_POP_BX,
	OPCODE_POP_SP,
	OPCODE_POP_BP,
	OPCODE_POP_SI,
	OPCODE_POP_DI,
	OPCODE_PUSHA = 0x60,
	OPCODE_POPA,
	OPCODE_PUSH_IMM32 = 0x68,
	OPCODE_PUSH_IMM8 = 0x6A,
	OPCODE_ADD_EB_IB = 0x80,
	OPCODE_ADD_EV_IV,
	OPCODE_MOV_EB_GB = 0x88,
	OPCODE_MOV_EV_GV,
	OPCODE_MOV_GB_EB,
	OPCODE_MOV_GV_EV,
	OPCODE_MOV_EW_SW,
	OPCODE_LEA_GV_M,
	OPCODE_MOV_SW_EW,
	OPCODE_MOV_AL_OB = 0xA0,
	OPCODE_MOV_AX_OV,
	OPCODE_MOV_OB_AL,
	OPCODE_MOV_OV_AX,
	OPCODE_MOV_AL_IB = 0xB0,
	OPCODE_MOV_CL_IB,
	OPCODE_MOV_DL_IB,
	OPCODE_MOV_BL_IB,
	OPCODE_MOV_AH_IB,
	OPCODE_MOV_CH_IB,
	OPCODE_MOV_DH_IB,
	OPCODE_MOV_BH_IB,
	OPCODE_MOV_AX_IV,
	OPCODE_MOV_CX_IV,
	OPCODE_MOV_DX_IV,
	OPCODE_MOV_BX_IV,
	OPCODE_MOV_SP_IV,
	OPCODE_MOV_BP_IV,
	OPCODE_MOV_SI_IV,
	OPCODE_MOV_DI_IV,
	OPCODE_MOV_EB_IV = 0xC6,
	OPCODE_MOV_EV_IV,
	OPCODE_LEAVE = 0xC9,
	OPCODE_INT3 = 0xCC,
	OPCODE_INTO,
	OPCODE_IRET = 0xCF,
	OPCODE_SALC = 0xD6,
	OPCODE_XLAT,
	OPCODE_NOP = 0x90,
	OPCODE_CBW = 0x98,
	OPCODE_CWD,
	OPCODE_WAIT = 0x9B,
	OPCODE_SAHF = 0x9E,
	OPCODE_LAHF,
	OPCODE_INT1 = 0xF1,
	OPCODE_HLT = 0xF4,
	OPCODE_CMC,
	OPCODE_CLC = 0xF8,
	OPCODE_STC,
	OPCODE_STI = 0xFB,
	OPCODE_CLI,
	OPCODE_CLD,
	OPCODE_STD,
	NUM_X86_OPCODES,
	BAD_OPCODE = -1,
	UNIDENTIFIED_OPCODE = -2,
};

enum eRegister
{
	AL,		CL,		DL,		BL,		AH,		CH,		DH,		BH,
	AX,		CX,		DX,		BX,		SP,		BP,		SI,		DI,
	EAX,	ECX,	EDX,	EBX,	ESP,	EBP,	ESI,	EDI,
	NUM_REGISTERS,	BAD_REGISTER = -1,
};

enum eSegment
{
	DS,		ES,		FS,		GS,		SS,		CS,		IP,		NUM_SEGMENTS,
	BAD_SEGMENT = -1,
};

/*enum eOperandType
{
	OPERAND_REGISTER_AX,
	OPERAND_REGISTER_CX,
	OPERAND_REGISTER_DX,
	OPERAND_REGISTER_BX,
	OPERAND_REGISTER_SP,
	OPERAND_REGISTER_BP,
	OPERAND_REGISTER_SI,
	OPERAND_REGISTER_DI,
	OPERAND_SEGMENT_DS,
	OPERAND_SEGMENT_ES,
	OPERAND_SEGMENT_FS,
	OPERAND_SEGMENT_GS,
	OPERAND_SEGMENT_SS,
	OPERAND_SEGMENT_CS,
	OPERAND_GENERAL_BYTE,
	OPERAND_EXTENDED_BYTE,
	OPERAND_GENERAL_VAL,
	OPERAND_EXTENDED_VAL,
	OPERAND_NOMODREGRM,
	//OPERAND_REGISTER_BYTE,
	//OPERAND_REGISTER32,
	OPERAND_VALUE8,
	OPERAND_VALUE16,
	OPERAND_VALUE32,
	BAD_OPERAND = -1,
};*/

struct sMnemonicTableEntry
{
	char mnemonic[8];
	eMnemonicInstruction instruction;
};

struct sRegisterTableEntry
{
	char szRegister[4];
	eRegister nRegister;
};

struct sSegment
{
	char szSegment[3];
	eSegment nSegment;
};

struct sModRegRm
{
	enum eReg : BYTE
	{
		AX, CX, DX, BX, SP, BP, SI, DI
	};
	enum eMod : BYTE
	{
		REGISTER_INDIRECT,
		BYTE_DISPLACEMENT,
		DWORD_DISPLACEMENT,
		REGISTER
	};

	eReg reg : 3;
	eReg rm : 3;
	eMod mod : 3;

	BYTE GetByte()
	{
		BYTE modregrem = reg | (rm << 3) | (mod << 6);
		return modregrem;
	}
};

enum eOperandType
{
	ASM_NONE,
	ASM_INSTRUCTION,
	ASM_REGISTER,
	ASM_SEGMENT,
	ASM_VALUE,
	ASM_DISPLACE_BEGIN,
	ASM_DISPLACE_END,
	ASM_UNKNOWN
};

struct sOperand
{
	bool used;
	eOperandType type;
	int size;
};

struct sOpcodeDefinition
{
	bool used;
	BYTE opcode;
	BYTE extended_opcode;
	BYTE num_operands;
	sOperand operands[5];
};

struct sInstructionDefinition
{
	eMnemonicInstruction instruction;
	sOpcodeDefinition opcodes[16];
};

class COperand
{
	eOperandType	m_bType;

	union
	{
		eMnemonicInstruction	m_bInstruction;
		eRegister				m_bRegister;
		eSegment				m_bSegment;
		int						m_nValue;
	};

public:
	void SetType(eOperandType type)
	{
		m_bType = type;
		m_nValue = NULL;
	}

	eOperandType GetType()
	{
		return m_bType;
	}

	int GetValue()							{return m_nValue;}
	eMnemonicInstruction GetInstruction()
	{
		if(m_bType != ASM_INSTRUCTION) throw("Expected instruction");
		return m_bInstruction;
	}
	eRegister GetRegister()
	{
		if(m_bType != ASM_REGISTER) throw("Expected register");
		return m_bRegister;
	}
	eSegment GetSegment()
	{
		if(m_bType != ASM_SEGMENT) throw("Expected segment");
		return m_bSegment;
	}

	void SetValue(eMnemonicInstruction v)	{m_bInstruction = v;}
	void SetValue(eRegister v)				{m_bRegister = v;}
	void SetValue(eSegment v)				{m_bSegment = v;}
	void SetValue(int v)					{m_nValue = v;}
};

class CAssembler : public CParser
{
	static eMnemonicInstruction GetInstruction(CToken *);
	static eRegister GetRegister(CToken *);
	static eSegment GetSegment(CToken *);
	static sOpcodeDefinition GetOpcodeDefinition(CToken *);
	static eMnemonicInstruction GetInstruction(const char *);
	static eRegister GetRegister(const char *);
	static eSegment GetSegment(const char *);
	static eOperandType GetRegisterOperandType(eRegister reg);
	static eOperandType ConvertRegisterToOperand(eRegister reg);
	static sInstructionDefinition * GetInstructionDefinition(eMnemonicInstruction);
	static sModRegRm::eReg GetRegisterID(eRegister reg);
	static bool AreOperandsCompatible(eOperandType ot1, eOperandType ot2);
	static int GetRegisterDataSize(eRegister reg);

	static bool					CollectIsDisplace()
	{
		return s_pOperands[s_nOperandIndex++].GetType() == ASM_DISPLACE_BEGIN;
	}

	static eRegister			CollectRegister()
	{
		return s_pOperands[s_nOperandIndex++].GetRegister();
	}
	
	static eMnemonicInstruction	CollectInstruction()
	{
		return s_pOperands[s_nOperandIndex++].GetInstruction();
	}

	static eSegment				CollectSegment()
	{
		return s_pOperands[s_nOperandIndex++].GetSegment();
	}

	static int					CollectValue()
	{
		return s_pOperands[s_nOperandIndex++].GetValue();
	}

	static eOperandType			GetOperandType()
	{
		return s_pOperands[s_nOperandIndex].GetType();
	}

	static int			s_nOperandIndex;
	static COperand		s_pOperands[MAX_TOKENS];

public:
	static int AssembleLine(CToken **);
};